فیکچرهای اصلی pytest برای تست کارآمد و قابل نگهداری. اصول تزریق وابستگی و مثال های عملی را برای نوشتن تست های قوی و قابل اعتماد بیاموزید.
فیکچرهای Pytest: تزریق وابستگی برای تست قوی
در قلمرو توسعه نرم افزار، تست قوی و قابل اعتماد بسیار مهم است. Pytest، یک چارچوب تست پایتون محبوب، یک ویژگی قدرتمند به نام فیکچرها ارائه می دهد که راه اندازی و جمع آوری تست را ساده می کند، قابلیت استفاده مجدد از کد را افزایش می دهد و قابلیت نگهداری تست را بهبود می بخشد. این مقاله به مفهوم فیکچرهای pytest می پردازد، نقش آنها را در تزریق وابستگی بررسی می کند و مثال های عملی برای نشان دادن اثربخشی آنها ارائه می دهد.
فیکچرهای Pytest چیست؟
در هسته خود، فیکچرهای pytest توابعی هستند که یک پایه ثابت را برای اجرای مطمئن و مکرر تست ها فراهم می کنند. آنها به عنوان مکانیزمی برای تزریق وابستگی عمل می کنند و به شما امکان می دهند منابع یا پیکربندی های قابل استفاده مجدد را تعریف کنید که به راحتی توسط چندین تابع تست قابل دسترسی هستند. آنها را به عنوان کارخانه هایی در نظر بگیرید که محیطی را که تست های شما برای اجرای صحیح به آن نیاز دارند، آماده می کنند.
برخلاف روش های راه اندازی و جمع آوری سنتی (مانند setUp
و tearDown
در unittest
)، فیکچرهای pytest انعطاف پذیری، مدولاریته و سازماندهی کد بیشتری را ارائه می دهند. آنها شما را قادر می سازند وابستگی ها را به صراحت تعریف کرده و چرخه عمر آنها را به روشی تمیز و مختصر مدیریت کنید.
تزریق وابستگی توضیح داده شد
تزریق وابستگی یک الگوی طراحی است که در آن اجزا وابستگی های خود را از منابع خارجی دریافت می کنند تا اینکه خودشان آنها را ایجاد کنند. این امر باعث ایجاد پیوند سست می شود و کد را مدولارتر، قابل آزمایش تر و قابل نگهداری تر می کند. در زمینه تست، تزریق وابستگی به شما این امکان را می دهد که به راحتی وابستگی های واقعی را با اشیاء Mock یا Test Doubles جایگزین کنید و به شما این امکان را می دهد که واحدهای جداگانه کد را جدا کرده و آزمایش کنید.
فیکچرهای Pytest به طور یکپارچه تزریق وابستگی را با ارائه مکانیزمی برای توابع تست برای اعلام وابستگی های خود تسهیل می کنند. هنگامی که یک تابع تست یک فیکچر را درخواست می کند، pytest به طور خودکار تابع فیکچر را اجرا می کند و مقدار بازگشتی آن را به عنوان یک آرگومان به تابع تست تزریق می کند.
مزایای استفاده از فیکچرهای Pytest
استفاده از فیکچرهای pytest در گردش کار تست شما مزایای بسیاری را ارائه می دهد:
- قابلیت استفاده مجدد از کد: فیکچرها را می توان در چندین تابع تست استفاده مجدد کرد، و حذف دوباره کاری کد و ترویج سازگاری.
- قابلیت نگهداری تست: تغییرات در وابستگی ها را می توان در یک مکان واحد (تعریف فیکچر) انجام داد، و خطر خطاها را کاهش داده و تعمیر و نگهداری را ساده می کند.
- خوانایی بهبود یافته: فیکچرها توابع تست را خواناتر و متمرکزتر می کنند، زیرا آنها به صراحت وابستگی های خود را اعلام می کنند.
- راه اندازی و جمع آوری ساده: فیکچرها به طور خودکار منطق راه اندازی و جمع آوری را مدیریت می کنند، و کد Boilerplate را در توابع تست کاهش می دهند.
- پارامتری سازی: فیکچرها می توانند پارامتری شوند، و به شما این امکان را می دهند که تست ها را با مجموعه های مختلف داده های ورودی اجرا کنید.
- مدیریت وابستگی: فیکچرها یک روش واضح و صریح برای مدیریت وابستگی ها ارائه می دهند، و درک و کنترل محیط تست را آسان تر می کنند.
مثال فیکچر پایه
بیایید با یک مثال ساده شروع کنیم. فرض کنید شما نیاز به آزمایش تابعی دارید که با یک پایگاه داده تعامل دارد. می توانید یک فیکچر را برای ایجاد و پیکربندی اتصال پایگاه داده تعریف کنید:
import pytest
import sqlite3
@pytest.fixture
def db_connection():
# Setup: create a database connection
conn = sqlite3.connect(':memory:') # Use an in-memory database for testing
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT,
email TEXT
)
""")
conn.commit()
# Provide the connection object to the tests
yield conn
# Teardown: close the connection
conn.close()
def test_add_user(db_connection):
cursor = db_connection.cursor()
cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", ('John Doe', 'john.doe@example.com'))
db_connection.commit()
cursor.execute("SELECT * FROM users WHERE name = ?", ('John Doe',))
result = cursor.fetchone()
assert result is not None
assert result[1] == 'John Doe'
assert result[2] == 'john.doe@example.com'
در این مثال:
@pytest.fixture
دکوراتور تابعdb_connection
را به عنوان یک فیکچر علامت گذاری می کند.- فیکچر یک اتصال پایگاه داده SQLite در حافظه ایجاد می کند، یک جدول
users
ایجاد می کند و شی اتصال را تولید می کند. - بیانیه
yield
مراحل راه اندازی و جمع آوری را جدا می کند. کد قبل ازyield
قبل از آزمایش اجرا می شود و کد بعد ازyield
پس از آزمایش اجرا می شود. - تابع
test_add_user
فیکچرdb_connection
را به عنوان یک آرگومان درخواست می کند. - Pytest به طور خودکار فیکچر
db_connection
را قبل از اجرای تست اجرا می کند و شی اتصال پایگاه داده را به تابع تست ارائه می دهد. - پس از اتمام آزمایش، pytest کد جمع آوری را در فیکچر اجرا می کند و اتصال پایگاه داده را می بندد.
دامنه فیکچر
فیکچرها می توانند دامنه های مختلفی داشته باشند که تعیین می کند هر چند وقت یکبار اجرا می شوند:
- function (پیش فرض): فیکچر یک بار در هر تابع تست اجرا می شود.
- class: فیکچر یک بار در هر کلاس تست اجرا می شود.
- module: فیکچر یک بار در هر ماژول اجرا می شود.
- session: فیکچر یک بار در هر جلسه تست اجرا می شود.
می توانید دامنه یک فیکچر را با استفاده از پارامتر scope
مشخص کنید:
import pytest
@pytest.fixture(scope="module")
def module_fixture():
# Setup code (executed once per module)
print("Module setup")
yield
# Teardown code (executed once per module)
print("Module teardown")
def test_one(module_fixture):
print("Test one")
def test_two(module_fixture):
print("Test two")
در این مثال، module_fixture
فقط یک بار در هر ماژول اجرا می شود، صرف نظر از اینکه چند تابع تست آن را درخواست می کنند.
پارامتری سازی فیکچر
فیکچرها را می توان برای اجرای تست ها با مجموعه های مختلف داده های ورودی پارامتری کرد. این برای آزمایش یک کد با پیکربندی ها یا سناریوهای مختلف مفید است.
import pytest
@pytest.fixture(params=[1, 2, 3])
def number(request):
return request.param
def test_number(number):
assert number > 0
در این مثال، فیکچر number
با مقادیر 1، 2 و 3 پارامتری شده است. تابع test_number
سه بار اجرا می شود، یک بار برای هر مقدار از فیکچر number
.
همچنین می توانید از pytest.mark.parametrize
برای پارامتری کردن مستقیم توابع تست استفاده کنید:
import pytest
@pytest.mark.parametrize("number", [1, 2, 3])
def test_number(number):
assert number > 0
این همان نتیجه استفاده از یک فیکچر پارامتری شده را به دست می آورد، اما اغلب برای موارد ساده راحت تر است.
استفاده از شی `request`
شی `request`، که به عنوان یک آرگومان در توابع فیکچر در دسترس است، دسترسی به اطلاعات زمینه ای مختلفی را در مورد تابع تستی که فیکچر را درخواست می کند، فراهم می کند. این یک نمونه از کلاس `FixtureRequest` است و به فیکچرها اجازه می دهد تا پویاتر و سازگارتر با سناریوهای مختلف تست باشند.
موارد استفاده رایج برای شی `request` عبارتند از:
- دسترسی به نام تابع تست:
request.function.__name__
نام تابع تستی را که از فیکچر استفاده می کند، ارائه می دهد. - دسترسی به اطلاعات ماژول و کلاس: می توانید با استفاده از
request.module
وrequest.cls
به ماژول و کلاسی که حاوی تابع تست هستند، دسترسی پیدا کنید. - دسترسی به پارامترهای فیکچر: هنگام استفاده از فیکچرهای پارامتری شده،
request.param
به شما امکان دسترسی به مقدار پارامتر فعلی را می دهد. - دسترسی به گزینه های خط فرمان: می توانید با استفاده از
request.config.getoption()
به گزینه های خط فرمانی که به pytest ارسال شده اند، دسترسی پیدا کنید. این برای پیکربندی فیکچرها بر اساس تنظیمات مشخص شده توسط کاربر مفید است. - اضافه کردن Finalizers:
request.addfinalizer(finalizer_function)
به شما امکان می دهد تابعی را ثبت کنید که پس از اتمام تابع تست، صرف نظر از اینکه تست قبول شده یا رد شده است، اجرا می شود. این برای کارهای تمیزکاری که همیشه باید انجام شوند، مفید است.
مثال:
import pytest
@pytest.fixture(scope="function")
def log_file(request):
test_name = request.function.__name__
filename = f"log_{test_name}.txt"
file = open(filename, "w")
def finalizer():
file.close()
print(f"\nClosed log file: {filename}")
request.addfinalizer(finalizer)
return file
def test_with_logging(log_file):
log_file.write("This is a test log message\n")
assert True
در این مثال، فیکچر `log_file` یک فایل گزارش مخصوص نام تابع تست ایجاد می کند. تابع `finalizer` تضمین می کند که فایل گزارش پس از اتمام تست بسته می شود و از `request.addfinalizer` برای ثبت تابع تمیزکاری استفاده می کند.
موارد استفاده رایج از فیکچر
فیکچرها همه کاره هستند و می توانند در سناریوهای مختلف تست استفاده شوند. در اینجا چند مورد استفاده رایج آورده شده است:
- اتصالات پایگاه داده: همانطور که در مثال قبلی نشان داده شد، فیکچرها می توانند برای ایجاد و مدیریت اتصالات پایگاه داده استفاده شوند.
- کلاینت های API: فیکچرها می توانند کلاینت های API را ایجاد و پیکربندی کنند و یک رابط سازگار برای تعامل با سرویس های خارجی ارائه دهند. برای مثال، هنگام آزمایش یک پلتفرم تجارت الکترونیک در سطح جهانی، ممکن است فیکچرهایی برای نقاط پایانی API منطقه ای مختلف داشته باشید (به عنوان مثال، `api_client_us()`, `api_client_eu()`, `api_client_asia()`).
- تنظیمات پیکربندی: فیکچرها می توانند تنظیمات پیکربندی را بارگیری و ارائه دهند و به تست ها اجازه می دهند با پیکربندی های مختلف اجرا شوند. به عنوان مثال، یک فیکچر می تواند تنظیمات پیکربندی را بر اساس محیط (توسعه، تست، تولید) بارگیری کند.
- اشیاء Mock: فیکچرها می توانند اشیاء Mock یا Test Doubles ایجاد کنند و به شما این امکان را می دهند که واحدهای جداگانه کد را جدا کرده و آزمایش کنید.
- فایل های موقت: فیکچرها می توانند فایل ها و دایرکتوری های موقت ایجاد کنند و یک محیط تمیز و مجزا برای تست های مبتنی بر فایل ارائه دهند. آزمایش تابعی را در نظر بگیرید که فایل های تصویر را پردازش می کند. یک فیکچر می تواند مجموعه ای از فایل های تصویر نمونه (به عنوان مثال، JPEG، PNG، GIF) با ویژگی های مختلف برای استفاده در تست ایجاد کند.
- احراز هویت کاربر: فیکچرها می توانند احراز هویت کاربر را برای آزمایش برنامه های وب یا API ها مدیریت کنند. یک فیکچر ممکن است یک حساب کاربری ایجاد کند و یک توکن احراز هویت برای استفاده در تست های بعدی به دست آورد. هنگام آزمایش برنامه های چند زبانه، یک فیکچر می تواند کاربران احراز هویت شده با تنظیمات زبان مختلف ایجاد کند تا از بومی سازی مناسب اطمینان حاصل کند.
تکنیک های پیشرفته فیکچر
Pytest چندین تکنیک پیشرفته فیکچر را برای بهبود قابلیت های تست شما ارائه می دهد:
- Fixture Autouse: می توانید از پارامتر
autouse=True
برای اعمال خودکار یک فیکچر به تمام توابع تست در یک ماژول یا جلسه استفاده کنید. از این کار با احتیاط استفاده کنید، زیرا وابستگی های ضمنی می توانند درک تست ها را دشوارتر کنند. - فضاهای نام فیکچر: فیکچرها در یک فضای نام تعریف می شوند که می تواند برای جلوگیری از تضاد نام ها و سازماندهی فیکچرها در گروه های منطقی استفاده شود.
- استفاده از فیکچرها در Conftest.py: فیکچرهای تعریف شده در
conftest.py
به طور خودکار برای تمام توابع تست در همان دایرکتوری و زیر شاخه های آن در دسترس هستند. این مکان خوبی برای تعریف فیکچرهای متداول استفاده شده است. - اشتراک گذاری فیکچرها در پروژه ها: می توانید کتابخانه های فیکچر قابل استفاده مجدد ایجاد کنید که می توانند در چندین پروژه به اشتراک گذاشته شوند. این امر باعث استفاده مجدد از کد و سازگاری می شود. ایجاد یک کتابخانه از فیکچرهای پایگاه داده متداول را در نظر بگیرید که می تواند در چندین برنامه کاربردی که با همان پایگاه داده تعامل دارند استفاده شود.
مثال: تست API با فیکچرها
بیایید تست API را با فیکچرها با استفاده از یک مثال فرضی نشان دهیم. فرض کنید شما در حال آزمایش یک API برای یک پلتفرم تجارت الکترونیک جهانی هستید:
import pytest
import requests
BASE_URL = "https://api.example.com"
@pytest.fixture
def api_client():
session = requests.Session()
session.headers.update({"Content-Type": "application/json"})
return session
@pytest.fixture
def product_data():
return {
"name": "Global Product",
"description": "A product available worldwide",
"price": 99.99,
"currency": "USD",
"available_countries": ["US", "EU", "Asia"]
}
def test_create_product(api_client, product_data):
response = api_client.post(f"{BASE_URL}/products", json=product_data)
assert response.status_code == 201
data = response.json()
assert data["name"] == "Global Product"
def test_get_product(api_client, product_data):
# First, create the product (assuming test_create_product works)
response = api_client.post(f"{BASE_URL}/products", json=product_data)
product_id = response.json()["id"]
# Now, get the product
response = api_client.get(f"{BASE_URL}/products/{product_id}")
assert response.status_code == 200
data = response.json()
assert data["name"] == "Global Product"
در این مثال:
- فیکچر
api_client
یک جلسه درخواست قابل استفاده مجدد با یک نوع محتوای پیش فرض ایجاد می کند. - فیکچر
product_data
یک بارگذاری محصول نمونه برای ایجاد محصولات ارائه می دهد. - تست ها از این فیکچرها برای ایجاد و بازیابی محصولات استفاده می کنند و از تعاملات API تمیز و سازگار اطمینان حاصل می کنند.
بهترین شیوه ها برای استفاده از فیکچرها
برای به حداکثر رساندن مزایای فیکچرهای pytest، این بهترین شیوه ها را دنبال کنید:
- فیکچرها را کوچک و متمرکز نگه دارید: هر فیکچر باید یک هدف واضح و خاص داشته باشد. از ایجاد فیکچرهای بیش از حد پیچیده که کارهای زیادی انجام می دهند، خودداری کنید.
- از نام های معنادار فیکچر استفاده کنید: نام های توصیفی را برای فیکچرهای خود انتخاب کنید که به وضوح هدف آنها را نشان می دهد.
- از عوارض جانبی خودداری کنید: فیکچرها باید در درجه اول بر تنظیم و ارائه منابع تمرکز کنند. از انجام اقداماتی که می توانند عوارض جانبی ناخواسته ای بر سایر تست ها داشته باشند، خودداری کنید.
- فیکچرهای خود را مستند کنید: Docstring ها را به فیکچرهای خود اضافه کنید تا هدف و استفاده آنها را توضیح دهید.
- از دامنه های فیکچر به طور مناسب استفاده کنید: دامنه فیکچر مناسب را بر اساس اینکه هر چند وقت یکبار فیکچر باید اجرا شود، انتخاب کنید. اگر یک فیکچر با دامنه عملکرد کافی باشد، از یک فیکچر با دامنه جلسه استفاده نکنید.
- جداسازی تست را در نظر بگیرید: اطمینان حاصل کنید که فیکچرهای شما جداسازی کافی بین تست ها را برای جلوگیری از تداخل فراهم می کنند. به عنوان مثال، از یک پایگاه داده جداگانه برای هر تابع یا ماژول تست استفاده کنید.
نتیجه
فیکچرهای Pytest ابزاری قدرتمند برای نوشتن تست های قوی، قابل نگهداری و کارآمد هستند. با پذیرش اصول تزریق وابستگی و استفاده از انعطاف پذیری فیکچرها، می توانید به طور قابل توجهی کیفیت و قابلیت اطمینان نرم افزار خود را بهبود بخشید. فیکچرها از مدیریت اتصالات پایگاه داده گرفته تا ایجاد اشیاء Mock، روشی تمیز و سازمان یافته برای مدیریت راه اندازی و جمع آوری تست ارائه می دهند و منجر به توابع تست خواناتر و متمرکزتر می شوند.
با پیروی از بهترین شیوه های ذکر شده در این مقاله و کاوش در تکنیک های پیشرفته موجود، می توانید پتانسیل کامل فیکچرهای pytest را باز کنید و قابلیت های تست خود را ارتقا دهید. به یاد داشته باشید که استفاده مجدد از کد، جداسازی تست و مستندسازی واضح را در اولویت قرار دهید تا یک محیط تست ایجاد کنید که هم موثر و هم آسان برای نگهداری باشد. همانطور که به ادغام فیکچرهای pytest در گردش کار تست خود ادامه می دهید، خواهید فهمید که آنها دارایی ضروری برای ساخت نرم افزار با کیفیت بالا هستند.
در نهایت، تسلط بر فیکچرهای pytest یک سرمایه گذاری در فرآیند توسعه نرم افزار شما است که منجر به افزایش اطمینان به کد شما و مسیری هموارتر برای ارائه برنامه های کاربردی قابل اعتماد و قوی به کاربران در سراسر جهان می شود.